#include "oled.h"

static uint8_t dc_state = 0;
static int fd_spidev;
static uint8_t (*fb_buffer)[128];

static void pin_init(void)
{
	char cmd[100];

	snprintf(cmd, sizeof(cmd), "echo %d > /sys/class/gpio/export", DC_PIN);
	system(cmd);
	snprintf(cmd, sizeof(cmd), "echo out > /sys/class/gpio/gpio%d/direction", DC_PIN);
	system(cmd);
	snprintf(cmd, sizeof(cmd), "echo 0 > /sys/class/gpio/gpio%d/value", DC_PIN);
	system(cmd);
	dc_state = 0;

	snprintf(cmd, sizeof(cmd), "echo %d > /sys/class/gpio/export", RST_PIN);
	system(cmd);
	snprintf(cmd, sizeof(cmd), "echo out > /sys/class/gpio/gpio%d/direction", RST_PIN);
	system(cmd);
	snprintf(cmd, sizeof(cmd), "echo 0 > /sys/class/gpio/gpio%d/value", RST_PIN);
	system(cmd);
}

static void pin_deinit(void)
{
	char cmd[100];

	snprintf(cmd, sizeof(cmd), "echo %d > /sys/class/gpio/unexport", DC_PIN);
	system(cmd);

	snprintf(cmd, sizeof(cmd), "echo %d > /sys/class/gpio/unexport", RST_PIN);
	system(cmd);
}

static void dc_high(void)
{
	char cmd[100];

	if(dc_state == 1)
		return;
	snprintf(cmd, sizeof(cmd), "echo 1 > /sys/class/gpio/gpio%d/value", DC_PIN);
	system(cmd);
	dc_state = 1;
}

static void dc_low(void)
{
	char cmd[100];

	if(dc_state == 0)
		return;
	snprintf(cmd, sizeof(cmd), "echo 0 > /sys/class/gpio/gpio%d/value", DC_PIN);
	system(cmd);
	dc_state = 0;
}

static void rst_high(void)
{
	char cmd[100];

	snprintf(cmd, sizeof(cmd), "echo 1 > /sys/class/gpio/gpio%d/value", RST_PIN);
	system(cmd);
}

static void rst_low(void)
{
	char cmd[100];

	snprintf(cmd, sizeof(cmd), "echo 0 > /sys/class/gpio/gpio%d/value", RST_PIN);
	system(cmd);
}

static void oled_reset(void)
{
	//暂时拉高复位引脚
	rst_high();
	usleep(200000);

	//拉低复位引脚，进行复位
	rst_low();
	usleep(200000);
	//拉高复位引脚，复位结束
	rst_high();
	usleep(200000);
}

static int oled_write_cmd(const unsigned char *buf, int len)
{
	//拉低数据命令选择引脚，表示发送命令
	dc_low();
	//通过spidev发送
	if(write(fd_spidev, buf, len) != len) 
		return -1;
	else
		return 0;
}

static int oled_write_data(const unsigned char *buf, int len)
{
	//拉高数据命令选择引脚，表示发送数据
	dc_high();
	//通过spidev发送
	if(write(fd_spidev, buf, len) != len) 
		return -1;
	else
		return 0;
}

int oled_init(const char *file)
{
	int result;
	uint8_t command[28];

	//初始化引脚
	pin_init();

	//打开spidev
	fd_spidev = open(file, O_RDWR);
	if(fd_spidev < 0) {
		pin_deinit();
		printf("open %s failed\n", file);
		return -1;
	}

	//分配显存
	fb_buffer = malloc(OLED_DISPLAY_RAM_SIZE);
	if(!fb_buffer)
	{
		pin_deinit();
		close(fd_spidev);
		printf("alloc memory failed\n");
		return -1;
	}
	memset(fb_buffer, 0, OLED_DISPLAY_RAM_SIZE);

	//复位OLED
	oled_reset();

	//发送初始化命令
	command[ 0 ] = 0xAE;				//--turn off oled panel
	command[ 1 ] = 0x00;				//---set low column address
	command[ 2 ] = 0x10;				//---set high column address
	command[ 3 ] = 0x40;				//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
	command[ 4 ] = 0x81;				//--set contrast control register
	command[ 5 ] = 0xCF;				 // Set SEG Output Current Brightness
	command[ 6 ] = 0xA1;				//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
	command[ 7 ] = 0xC8;				//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
	command[ 8 ] = 0xA6;				//--set normal display
	command[ 9 ] = 0xA8;				//--set multiplex ratio(1 to 64)
	command[ 10] = 0x3F;				//--1/64 duty
	command[ 11] = 0xD3;				//-set display offset	Shift Mapping RAM Counter (0x00~0x3F)
	command[ 12] = 0x00;				//-not offset
	command[ 13] = 0xD5;				//--set display clock divide ratio/oscillator frequency
	command[ 14] = 0x80;				//--set divide ratio, Set Clock as 100 Frames/Sec
	command[ 15] = 0xD9;				//--set pre-charge period
	command[ 16] = 0xF1;				//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
	command[ 17] = 0xDA;				//--set com pins hardware configuration
	command[ 18] = 0x12;				
	command[ 19] = 0xDB;				//--set vcomh
	command[ 20] = 0x40;				//Set VCOM Deselect Level
	command[ 21] = 0x20;				//-Set Page Addressing Mode (0x00/0x01/0x02)
	command[ 22] = 0x00;				//
	command[ 23] = 0x8D;				//--set Charge Pump enable/disable
	command[ 24] = 0x14;				//--set(0x10) disable
	command[ 25] = 0xA4;				// Disable Entire Display On (0xa4/0xa5)
	command[ 26] = 0xA6;				// Disable Inverse Display On (0xa6/a7) 
	command[ 27] = 0xAF;				//--turn on oled panel 
	result = oled_write_cmd(command, 28);
	if(result != 0)
	{
		pin_deinit();
		free(fb_buffer);
		close(fd_spidev);
		printf("write cmd failed\n");
		return result;
	}

	//更新OLED显示
	result = oled_update();
	if(result != 0)
	{
		pin_deinit();
		free(fb_buffer);
		close(fd_spidev);
		printf("update display failed\n");
		return result;
	}

	return 0;
}

int oled_deinit(void)
{
	pin_deinit();
	close(fd_spidev);
	free(fb_buffer);

	return 0;
}

void oled_draw_point(uint8_t x, uint8_t y, int color)
{
	uint8_t page, offset;

	if((x > 127) || (y > 63))
		return;

	//计数页
	page = y / 8;
	//计数页内行
	offset = y % 8;
	if(color)
		fb_buffer[page][x] |= 0x01 << offset;
	else
		fb_buffer[page][x] &= ~(0x01 << offset);
}

int oled_read_point(uint8_t x, uint8_t y)
{
	uint8_t temp;
	uint8_t page, offset;

	if((x > 127) || (y > 63))
		return -1;

	//计数页
	page = y / 8;
	//计数页内行
	offset = y % 8;

	temp = fb_buffer[page][x];
	return  (temp >> offset) & 0x01;
}

void oled_clear(void)
{
	memset(fb_buffer, 0, OLED_DISPLAY_RAM_SIZE);
}

int oled_update(void)
{
	int i;
	int result;
	uint8_t command[3];

	//设置地址
	command[0] = 0x00;					//设置页地址
	command[1] = 0x10;					//设置显示位置—列高地址
	command[2] = 0x00;					//设置显示位置—列低地址
	result = oled_write_cmd(command, 3);
	if(result != 0)
		return result;
	for(i=0; i<8; i++)
	{
		//发送显示数据
		result = oled_write_data(fb_buffer[i], 128);
		if(result != 0)
			return result;
	}

	return 0;
}

void display_line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2)
{
	uint8_t deltax = 0, deltay = 0, x = 0, y = 0, xinc1 = 0, xinc2 = 0, \
	yinc1 = 0, yinc2 = 0, den = 0, num = 0, numadd = 0, numpixels = 0, \
	curpixel = 0;
	
	deltax = ABS(x2 - x1);			/* The difference between the x's */
	deltay = ABS(y2 - y1);			/* The difference between the y's */
	x = x1;							/* Start x off at the first pixel */
	y = y1;							/* Start y off at the first pixel */


	if (x2 >= x1)					/* The x-values are increasing */
	{
		xinc1 = 1;
		xinc2 = 1;
	}
	else							/* The x-values are decreasing */
	{
		xinc1 = -1;
		xinc2 = -1;
	}
	
	if (y2 >= y1)					/* The y-values are increasing */
	{
		yinc1 = 1;
		yinc2 = 1;
	}
	else							/* The y-values are decreasing */
	{
		yinc1 = -1;
		yinc2 = -1;
	}
	
	if (deltax >= deltay)			/* There is at least one x-value for every y-value */
	{
		xinc1 = 0;					/* Don't change the x when numerator >= denominator */
		yinc2 = 0;					/* Don't change the y for every iteration */
		den = deltax;
		num = deltax / 2;
		numadd = deltay;
		numpixels = deltax;			/* There are more x-values than y-values */
	}
	else							/* There is at least one y-value for every x-value */
	{
		xinc2 = 0;					/* Don't change the x for every iteration */
		yinc1 = 0;					/* Don't change the y when numerator >= denominator */
		den = deltay;
		num = deltay / 2;
		numadd = deltax;
		numpixels = deltay;			/* There are more y-values than x-values */
	}
	
	for(curpixel = 0; curpixel <= numpixels; curpixel++)
	{
		oled_draw_point( x, y, 1); 
		num += numadd;				/* Increase the numerator by the top of the fraction */
		if (num >= den)				/* Check if numerator >= denominator */
		{
			num -= den;				/* Calculate the new numerator value */
			x += xinc1;				/* Change the x as appropriate */
			y += yinc1;				/* Change the y as appropriate */
		}
		x += xinc2;					/* Change the x as appropriate */
		y += yinc2;					/* Change the y as appropriate */
	}
}

void display_rect(uint8_t x, uint8_t y, uint8_t width, uint8_t high)
{
	uint8_t w,h;

	for(h = 0; h < high; h++)
	{
		for(w = 0; w < width; w++)
		{
			oled_draw_point(x+w, y+h, 1);
		}
	}
}
